﻿/*
 * Copyright 2012 Rene Prost, Registrite ja Infosüsteemide Keskus 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Services.Description;

namespace Xtee.Core.Services.Description
{
    public class Operation
    {
        protected Operation(Port port, string version, string name)
        {
            Port = port; 
            Version = version;
            Name = name;
        }

        public string Name { get; private set; }
        public string Version { get; private set; }
        public Message Input { get; private set; }
        public Port Port { get; private set; }
        public Message Output { get; private set; }
        public Header InputHeader { get; private set; }
        public Header OutputHeader { get; private set; }
        public static Operation Create(Port port, OperationBinding operationBinding, bool justOperations)
        {
            var description = operationBinding.Binding.ServiceDescription;

            var op = new Operation(port, GetVersion(operationBinding), operationBinding.Name);

            if (operationBinding.Input == null)
                throw new ServiceDescriptionException("Couldn't find operation '{0}' input binding '{1}' as described in binding '{2}'.",
                                                      operationBinding.Name,
                                                      operationBinding.Binding.Type.Name,
                                                      operationBinding.Binding.Name);
            
            
            op.InputHeader = new Header(operationBinding.Input.Extensions.GetHeaderBindings(port.TargetNamespace), op,
                HeaderType.Input, port.Resolver);

            if (operationBinding.Output == null)
                throw new ServiceDescriptionException("Couldn't find operation '{0}' output binding '{1}' as described in binding '{2}'.",
                                                      operationBinding.Name,
                                                      operationBinding.Binding.Type.Name,
                                                      operationBinding.Binding.Name);

            op.OutputHeader = new Header(operationBinding.Output.Extensions.GetHeaderBindings(port.TargetNamespace), op,
                HeaderType.Output, port.Resolver);

            if (justOperations)
                return op;
            
            description.EnsureInTargetNamespace(operationBinding.Binding.Type, "PortType", "binding", operationBinding.Name);

            var portType = description.PortTypes.FirstOrDefault<PortType>(x => x.Name == operationBinding.Binding.Type.Name);

            if (portType == null)
            {
                throw new ServiceDescriptionException("Couldn't find portType '{0}' as referenced in binding '{1}'.",
                                                      operationBinding.Binding.Type.Name,
                                                      operationBinding.Binding.Name);
            }

            var operation = portType.Operations.FirstOrDefault<System.Web.Services.Description.Operation>(x => x.Name == operationBinding.Name);

            if (operation == null)
            {
                throw new ServiceDescriptionException("Couldn't find operation '{0}' in portType '{1}' as described in binding '{2}'.",
                                                      operationBinding.Name,
                                                      operationBinding.Binding.Type.Name,
                                                      operationBinding.Binding.Name);
            }



            var messages = operation.Messages.Cast<OperationMessage>().ToList();
            OperationInput operationInput = messages.OfType<OperationInput>().SingleOrDefault(); 
            OperationOutput operationOutput = messages.OfType<OperationOutput>().SingleOrDefault();
            

            var format = "Operation '{0}' {2} message type not specified in portType '{1}'.";
            if (operationInput == null)
            {
                throw new ServiceDescriptionException(format,
                                                      operationBinding.Name,
                                                      operationBinding.Binding.Type.Name,
                                                      "input");
            }
            if (operationOutput == null)
            {
                throw new ServiceDescriptionException(format,
                                                      operationBinding.Name,
                                                      operationBinding.Binding.Type.Name,
                                                      "output");
            }


            description.EnsureInTargetNamespace(operationInput.Message, "Message", "operation input", operationBinding.Name);
            description.EnsureInTargetNamespace(operationOutput.Message, "Message", "operation output", operationBinding.Name);
            var inputMessage = description.Messages.FirstOrDefault<System.Web.Services.Description.Message>(x => x.Name == operationInput.Message.Name);
            var outputMessage = description.Messages.FirstOrDefault<System.Web.Services.Description.Message>(x => x.Name == operationOutput.Message.Name);

            format = "Unable to locate {3} message '{0}' referenced in portType '{1}' -> operation '{2}'";
            if (inputMessage == null)
            {
                throw new ServiceDescriptionException(
                    format,
                    operationInput.Message.ToString(),
                    operationBinding.Binding.Type.Name,
                    operationBinding.Name,
                    "input");
            }

            if (outputMessage == null)
            {
                throw new ServiceDescriptionException(
                    format,
                    operationOutput.Message.ToString(),
                    operationBinding.Binding.Type.Name,
                    operationBinding.Name,
                    "output");
            }

            op.Input =  Message.Create(inputMessage, op, port.Resolver,false);
            op.Output =  Message.Create(outputMessage, op, port.Resolver,true);
            return op;
        }

        private static string GetVersion(OperationBinding operation)
        {
            var element = operation.Extensions.GetXteeExtensionElement("version");
            if (element != null)
            {
                return element.InnerText;
            }
            return null;
        }

    }
}